// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <zenr_api.h>

#define FLASHFX_IOCTL_BASE              0x4100
#define FLASHFX_IOCTL_DISCARD_SUPPORT   (FLASHFX_IOCTL_BASE + 1)
#define FLASHFX_IOCTL_DISCARD           (FLASHFX_IOCTL_BASE + 2)
#define FLASHFX_IOCTL_FLUSH             (FLASHFX_IOCTL_BASE + 3)
#define FLASHFX_IOCTL_PARAMS            (FLASHFX_IOCTL_BASE + 4)
#define FLASHFX_IOCTL_MEDIA_PARAMS      (FLASHFX_IOCTL_BASE + 5)
#define FLASHFX_IOCTL_PARTITION_PARAMS  (FLASHFX_IOCTL_BASE + 6)
#define FLASHFX_IOCTL_COMPACTION_SUSPEND  (FLASHFX_IOCTL_BASE + 7)
#define FLASHFX_IOCTL_COMPACTION_RESUME (FLASHFX_IOCTL_BASE + 8)
#define FLASHFX_IOCTL_FORMAT            (FLASHFX_IOCTL_BASE + 9)

class TiNand : public CDeviceDriver {
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf &buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf &inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);
    virtual void Dispose() {};

    TiNand(Int32 nDiskNum) : m_nDiskNum(nDiskNum), m_nSectorCount(0) {}
    ~TiNand() {
        kprintf("In ~TiNand(): Device[%d] \n", m_nDiskNum);
    }

    Int32 m_nDiskNum;
    UInt32 m_nSectorCount;
};

#define NU_PAGENUMBER_OF_BLOCK  64L              // a block = 64 pages
#define NU_PAGE_SIZE            2048L            // 2048 Bytes

ECode TiNand::Read(
            Int64 offset,
            Int32 bytesToRead,
            MemoryBuf * pBuffer,
            IEvent **ppCompletionEvent)
{
    Int64 lLen = 0, rLen = bytesToRead;

    UInt32 pageNumber;
    UInt32 offsetOfPage;
    UInt32 readLenOfPage;
    Byte pageBuffer[NU_PAGE_SIZE];

    if (m_nSectorCount <= (offset / NU_PAGE_SIZE) ||
        m_nSectorCount <= ((offset + rLen - 1) / NU_PAGE_SIZE) ||
        rLen == 0) {
        return E_INVALID_ARGUMENT;
    }

    PByte pByte = pBuffer->GetPayload();

    if ((offset & (NU_PAGE_SIZE - 1)) == 0 &&
                (rLen & (NU_PAGE_SIZE - 1)) == 0) {

        pageNumber = offset / NU_PAGE_SIZE;

        if (zapi_FS->FlashFXDeviceIO(m_nDiskNum, pageNumber,
                                    pByte, rLen / NU_PAGE_SIZE, TRUE) != 0) {
            kprintf("######## zapi_FS->FlashFXDeviceIO(%d, %d, %d, TRUE) failed!!!\n",
                m_nDiskNum, pageNumber, rLen/NU_PAGE_SIZE);
            return E_FS_IO_ERROR;
        }
        pBuffer->SetUsed(bytesToRead);
        return NOERROR;
    }

    do {
        pageNumber = offset / NU_PAGE_SIZE;
        offsetOfPage = offset  & (NU_PAGE_SIZE - 1);
        readLenOfPage = (rLen + offsetOfPage > NU_PAGE_SIZE)?
                             (NU_PAGE_SIZE - offsetOfPage) : rLen;

        if (zapi_FS->FlashFXDeviceIO(m_nDiskNum, pageNumber,
                                                    pageBuffer, 1, TRUE) != 0) {
            kprintf("######## zapi_FS->FlashFXDeviceIO(1, %d, TRUE) failed!!!\n",
                pageNumber);
            return E_FS_IO_ERROR;
        }
        memcpy(pByte+lLen, pageBuffer+offsetOfPage, readLenOfPage);

        lLen += readLenOfPage;
        rLen -= readLenOfPage;
        offset += readLenOfPage;
        pBuffer->SetUsed(lLen);

    } while( rLen > 0);

    return NOERROR;
}

ECode TiNand::Write(
            Int64 offset,
            const MemoryBuf & buffer,
            Int32 *pBytesWritten,
            IEvent **ppCompletionEvent)
{
    Int64 lLen = 0, rLen = buffer.GetUsed();

    UInt32 pageNumber;
    UInt32 offsetOfPage;
    UInt32 writeLenOfPage;
    Byte pageBuffer[NU_PAGE_SIZE];

    if (m_nSectorCount <= (offset / NU_PAGE_SIZE) ||
        m_nSectorCount <= ((offset + rLen - 1) / NU_PAGE_SIZE) ||
        rLen == 0) {
        return E_INVALID_ARGUMENT;
    }

    PByte pBuffer = buffer.GetPayload();

    if ( (offset & (NU_PAGE_SIZE - 1)) == 0 &&
                (rLen & (NU_PAGE_SIZE - 1)) == 0) {
        pageNumber = offset / NU_PAGE_SIZE;

        assert(m_nSectorCount > pageNumber);
        if (zapi_FS->FlashFXDeviceIO(m_nDiskNum, pageNumber,
                                    pBuffer, rLen / NU_PAGE_SIZE, FALSE) != 0) {
            kprintf("######## zapi_FS->FlashFXDeviceIO(%d, %d, %d, FALSE) failed!!!\n",
                m_nDiskNum, pageNumber, rLen/NU_PAGE_SIZE);
            return E_FS_IO_ERROR;
        }
        *pBytesWritten = rLen;
        return NOERROR;
    }

    do {
        pageNumber = offset / NU_PAGE_SIZE;
        offsetOfPage = offset  & (NU_PAGE_SIZE - 1);
        writeLenOfPage = (rLen + offsetOfPage > NU_PAGE_SIZE)?
                             (NU_PAGE_SIZE - offsetOfPage) : rLen;

        if (zapi_FS->FlashFXDeviceIO(m_nDiskNum, pageNumber,
                                                    pageBuffer, 1, TRUE) != 0) {
            kprintf("######## zapi_FS->FlashFXDeviceIO(1, %d, TRUE) failed!!!\n",
                pageNumber);
            return E_FS_IO_ERROR;
        }
        memcpy(pageBuffer+offsetOfPage, pBuffer+lLen, writeLenOfPage);
        if (zapi_FS->FlashFXDeviceIO(m_nDiskNum, pageNumber,
                                                pageBuffer, 1, FALSE) != 0) {
            kprintf("######## zapi_FS->FlashFXDeviceIO(1, %d, FALSE) failed!!!\n",
                                                pageNumber);
            return E_FS_IO_ERROR;
        }

        lLen += writeLenOfPage;
        rLen -= writeLenOfPage;
        offset += writeLenOfPage;

    } while( rLen > 0);


    *pBytesWritten = lLen;

    return NOERROR;
}

typedef struct disk_partition_params
{
    unsigned long   ulStartSector;
    unsigned long   ulSectorCount;
    unsigned char   ucBootable;
} DISK_PARTITION_PARAMS;

ECode TiNand::Control(
            Handle32 controlCode,
            const MemoryBuf & inBuffer,
            MemoryBuf * pOutBuffer,
            IEvent **ppCompletionEvent)
{
    if (controlCode > FLASHFX_IOCTL_BASE &&
        controlCode < FLASHFX_IOCTL_BASE + 0x1000) {

        if (zapi_FS->FlashFXDeviceIoctl(m_nDiskNum, controlCode, pOutBuffer->GetPayload()) != 0) {
            kprintf("Flash Driver ERROR: zapi_FS->FlashFXDeviceIoctl(%d, 0x%08x) failed\n",
                    m_nDiskNum, controlCode);
        }
        else {
            //kprintf("zapi_FS->FlashFXDeviceIoctl(%d, 0x%08x) OK\n", m_nDiskNum, controlCode);
        }
    }
    else {
        return E_INVALID_ARGUMENT;
    }


    return NOERROR;
}

EXTERN IDeviceDriver * CDECL CreateTiStorage(uint_t uDeviceNo, void *pvParameter)
{
    MemoryBuf_<128> buffer;
    TiNand *pTiNand = new TiNand(uDeviceNo);
    if (NULL == pTiNand) return NULL;
    pTiNand->AddRef();

    ECode ec = pTiNand->Control(FLASHFX_IOCTL_PARTITION_PARAMS,
                                           buffer, &buffer, NULL);
    if (FAILED(ec)) {
        kprintf("*ERROR* failed to call control, ec = 0x%08x\n", ec);
    }
    else {
        DISK_PARTITION_PARAMS* pDmp = (DISK_PARTITION_PARAMS*)buffer.GetPayload();

        pTiNand->m_nSectorCount = pDmp->ulSectorCount;

        kprintf("CreateTiStorage(%d) OK, partition start from %d sector, total %d sectors.\n",
                uDeviceNo, pDmp->ulStartSector, pDmp->ulSectorCount);
    }
    return pTiNand;
}

